<!DOCTYPE html>
<html lang="zh-CN" data-theme="light">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="generator" content="VuePress 2.0.0-beta.53" />
    <meta name="theme" content="VuePress Theme Hope" />
    <link rel="icon" href="/doc/images/favicon.ico"><title>第 6 章 类型进阶 | AT 的文档库</title><meta name="description" content="At 的文档库，好记性不如烂笔头！">
    <style>
      :root {
        --bg-color: #fff;
      }

      html[data-theme="dark"] {
        --bg-color: #1d2025;
      }

      html,
      body {
        background: var(--bg-color);
      }
    </style>
    <script>
      const userMode = localStorage.getItem("vuepress-theme-hope-scheme");
      const systemDarkMode =
        window.matchMedia &&
        window.matchMedia("(prefers-color-scheme: dark)").matches;

      if (userMode === "dark" || (userMode !== "light" && systemDarkMode)) {
        document.querySelector("html").setAttribute("data-theme", "dark");
      }
    </script>
    <link rel="preload" href="/doc/assets/style.c3a4c10b.css" as="style" /><link rel="stylesheet" href="/doc/assets/style.c3a4c10b.css" />
    <link rel="modulepreload" href="/doc/assets/app.41440b49.js"><link rel="modulepreload" href="/doc/assets/第 6 章 类型进阶.html.1b5b864e.js"><link rel="modulepreload" href="/doc/assets/_plugin-vue_export-helper.cdc0426e.js"><link rel="modulepreload" href="/doc/assets/第 6 章 类型进阶.html.45b313e3.js"><link rel="prefetch" href="/doc/assets/index.html.0bcc895f.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.c8df2167.js" as="script" /><link rel="prefetch" href="/doc/assets/Vue2.html.48e096c5.js" as="script" /><link rel="prefetch" href="/doc/assets/Vue3.html.324e646c.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.fb0ad0e0.js" as="script" /><link rel="prefetch" href="/doc/assets/第 1 章 导言.html.d6c662f8.js" as="script" /><link rel="prefetch" href="/doc/assets/第 2 章 TypeScript 概述.html.e976154a.js" as="script" /><link rel="prefetch" href="/doc/assets/第 3 章 类型全解.html.e4f1b931.js" as="script" /><link rel="prefetch" href="/doc/assets/第 4 章 函数.html.701a2968.js" as="script" /><link rel="prefetch" href="/doc/assets/第 5 章 类和接口.html.8988d546.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.a9dd6465.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.a3374cf8.js" as="script" /><link rel="prefetch" href="/doc/assets/面试题详解.html.c326eaaf.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.88582229.js" as="script" /><link rel="prefetch" href="/doc/assets/常见问题.html.e0f65550.js" as="script" /><link rel="prefetch" href="/doc/assets/扩展使用.html.87cccc41.js" as="script" /><link rel="prefetch" href="/doc/assets/掌握 GIT.html.c150b234.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.a3c2858b.js" as="script" /><link rel="prefetch" href="/doc/assets/面试题详解.html.9a53d157.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.101afd82.js" as="script" /><link rel="prefetch" href="/doc/assets/面试题详解.html.8224e1cd.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.54b5ca2d.js" as="script" /><link rel="prefetch" href="/doc/assets/基础掌握.html.d839cc53.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.80e4d5cc.js" as="script" /><link rel="prefetch" href="/doc/assets/基础掌握.html.9a178a04.js" as="script" /><link rel="prefetch" href="/doc/assets/扩展知识.html.3535ab30.js" as="script" /><link rel="prefetch" href="/doc/assets/项目搭建.html.e926587b.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.881e95b0.js" as="script" /><link rel="prefetch" href="/doc/assets/安装及使用.html.ba7314a3.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.f9509ce0.js" as="script" /><link rel="prefetch" href="/doc/assets/第 1 章.html.726a0bb9.js" as="script" /><link rel="prefetch" href="/doc/assets/第 2 章.html.38f55e7d.js" as="script" /><link rel="prefetch" href="/doc/assets/Controllers.html.6b27764c.js" as="script" /><link rel="prefetch" href="/doc/assets/Module.html.3849b10b.js" as="script" /><link rel="prefetch" href="/doc/assets/Providers.html.7d2793f1.js" as="script" /><link rel="prefetch" href="/doc/assets/404.html.c5de7dd9.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.86b08b0b.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.f4119260.js" as="script" /><link rel="prefetch" href="/doc/assets/Vue2.html.caf7eb18.js" as="script" /><link rel="prefetch" href="/doc/assets/Vue3.html.6dc23cf4.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.868f2c80.js" as="script" /><link rel="prefetch" href="/doc/assets/第 1 章 导言.html.9edf000e.js" as="script" /><link rel="prefetch" href="/doc/assets/第 2 章 TypeScript 概述.html.76b27e4d.js" as="script" /><link rel="prefetch" href="/doc/assets/第 3 章 类型全解.html.9ad8a3d4.js" as="script" /><link rel="prefetch" href="/doc/assets/第 4 章 函数.html.9115df00.js" as="script" /><link rel="prefetch" href="/doc/assets/第 5 章 类和接口.html.e392a0d1.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.e5a75968.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.974654cf.js" as="script" /><link rel="prefetch" href="/doc/assets/面试题详解.html.ed3fa7a3.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.3b9ebcaa.js" as="script" /><link rel="prefetch" href="/doc/assets/常见问题.html.b427e39a.js" as="script" /><link rel="prefetch" href="/doc/assets/扩展使用.html.76b3dd90.js" as="script" /><link rel="prefetch" href="/doc/assets/掌握 GIT.html.724416fc.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.8b55f9ef.js" as="script" /><link rel="prefetch" href="/doc/assets/面试题详解.html.8ee968a7.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.06371751.js" as="script" /><link rel="prefetch" href="/doc/assets/面试题详解.html.6b672044.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.b200eaff.js" as="script" /><link rel="prefetch" href="/doc/assets/基础掌握.html.5135d2c4.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.557b7bf5.js" as="script" /><link rel="prefetch" href="/doc/assets/基础掌握.html.7b535b09.js" as="script" /><link rel="prefetch" href="/doc/assets/扩展知识.html.c6b233af.js" as="script" /><link rel="prefetch" href="/doc/assets/项目搭建.html.450f0b1d.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.6483ed06.js" as="script" /><link rel="prefetch" href="/doc/assets/安装及使用.html.b4d859f3.js" as="script" /><link rel="prefetch" href="/doc/assets/index.html.f15cf3e9.js" as="script" /><link rel="prefetch" href="/doc/assets/第 1 章.html.127b1e7c.js" as="script" /><link rel="prefetch" href="/doc/assets/第 2 章.html.ea8e67e2.js" as="script" /><link rel="prefetch" href="/doc/assets/Controllers.html.029df1e7.js" as="script" /><link rel="prefetch" href="/doc/assets/Module.html.f102db2a.js" as="script" /><link rel="prefetch" href="/doc/assets/Providers.html.9bef3753.js" as="script" /><link rel="prefetch" href="/doc/assets/404.html.2cfd6b20.js" as="script" /><link rel="prefetch" href="/doc/assets/photoswipe.esm.09e03fed.js" as="script" />
  </head>
  <body>
    <div id="app"><!--[--><!--[--><!--[--><span tabindex="-1"></span><a href="#main-content" class="skip-link sr-only">Skip to content</a><!--]--><div class="theme-container has-toc"><!--[--><!--[--><header class="navbar"><div class="navbar-left"><button class="toggle-sidebar-button" title="Toggle Sidebar"><span class="icon"></span></button><!--[--><!----><!--]--><a href="/doc/" class="brand"><!----><!----><span class="site-name">AT 的文档库</span></a><!--[--><!----><!--]--></div><div class="navbar-center"><!--[--><!----><!--]--><nav class="nav-links"><div class="nav-item hide-in-mobile"><a href="/doc/" class="nav-link" aria-label="首页"><!---->首页<!----></a></div><div class="nav-item hide-in-mobile"><div class="dropdown-wrapper"><button class="dropdown-title" type="button" aria-label="开发问题汇总"><span class="title"><!---->开发问题汇总</span><span class="arrow"></span><ul class="nav-dropdown"><li class="dropdown-item"><a href="/doc/development-question/Vue" class="nav-link" aria-label="Vue"><!---->Vue<!----></a></li></ul></button></div></div><div class="nav-item hide-in-mobile"><div class="dropdown-wrapper"><button class="dropdown-title" type="button" aria-label="系统笔记"><span class="title"><!---->系统笔记</span><span class="arrow"></span><ul class="nav-dropdown"><li class="dropdown-item"><a href="/doc/sysnotes/Git/%E6%8E%8C%E6%8F%A1%20GIT.html" class="nav-link" aria-label="GIT"><!---->GIT<!----></a></li><li class="dropdown-item"><a href="/doc/sysnotes/Nvm/%E5%AE%89%E8%A3%85%E5%8F%8A%E4%BD%BF%E7%94%A8.html" class="nav-link" aria-label="NVM"><!---->NVM<!----></a></li><li class="dropdown-item"><a href="/doc/sysnotes/Linux/%E5%9F%BA%E7%A1%80%E6%8E%8C%E6%8F%A1.html" class="nav-link" aria-label="Linux"><!---->Linux<!----></a></li><li class="dropdown-item"><a href="/doc/sysnotes/JavaScript" class="nav-link" aria-label="JavaScript"><!---->JavaScript<!----></a></li><li class="dropdown-item"><a href="/doc/sysnotes/CSS" class="nav-link" aria-label="CSS"><!---->CSS<!----></a></li><li class="dropdown-item"><a href="/doc/sysnotes/HTML" class="nav-link" aria-label="HTML"><!---->HTML<!----></a></li></ul></button></div></div><div class="nav-item hide-in-mobile"><div class="dropdown-wrapper"><button class="dropdown-title" type="button" aria-label="阅读笔记"><span class="title"><!---->阅读笔记</span><span class="arrow"></span><ul class="nav-dropdown"><li class="dropdown-item"><a href="/doc/readbook/《Vue.js 设计与实现》/" class="nav-link" aria-label="《Vue.js 设计与实现》"><!---->《Vue.js 设计与实现》<!----></a></li><li class="dropdown-item"><a href="/doc/readbook/《TypeScript 编程》/" class="nav-link" aria-label="《TypeScript 编程》"><!---->《TypeScript 编程》<!----></a></li></ul></button></div></div></nav><!--[--><!----><!--]--></div><div class="navbar-right"><!--[--><!----><!--]--><div class="nav-item hide-in-mobile"><button id="appearance-switch"><svg xmlns="http://www.w3.org/2000/svg" class="icon auto-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="auto icon" style="display:block;"><path d="M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" class="icon dark-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="dark icon" style="display:none;"><path d="M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" class="icon light-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="light icon" style="display:none;"><path d="M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"></path></svg></button></div><form class="search-box" role="search"><input type="search" autocomplete="off" spellcheck="false" value><!----></form><!--[--><!----><!--]--><button class="toggle-navbar-button" aria-label="Toggle Navbar" aria-expanded="false" aria-controls="nav-screen"><span class="button-container"><span class="button-top"></span><span class="button-middle"></span><span class="button-bottom"></span></span></button></div></header><!----><!--]--><!----><div class="toggle-sidebar-wrapper"><span class="arrow left"></span></div><aside class="sidebar"><!--[--><!----><!--]--><ul class="sidebar-links"><li><section class="sidebar-group"><button class="sidebar-heading clickable active"><!----><span class="title">《TypeScript 编程》</span><span class="arrow down"></span></button><ul class="sidebar-links"><li><!--[--><a href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%201%20%E7%AB%A0%20%E5%AF%BC%E8%A8%80.html" class="nav-link sidebar-link sidebar-page" aria-label="第 1 章 导言"><!---->第 1 章 导言<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%202%20%E7%AB%A0%20TypeScript%20%E6%A6%82%E8%BF%B0.html" class="nav-link sidebar-link sidebar-page" aria-label="第 2 章 TypeScript 概述"><!---->第 2 章 TypeScript 概述<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%203%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E5%85%A8%E8%A7%A3.html" class="nav-link sidebar-link sidebar-page" aria-label="第 3 章 类型全解"><!---->第 3 章 类型全解<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%204%20%E7%AB%A0%20%E5%87%BD%E6%95%B0.html" class="nav-link sidebar-link sidebar-page" aria-label="第 4 章 函数"><!---->第 4 章 函数<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%205%20%E7%AB%A0%20%E7%B1%BB%E5%92%8C%E6%8E%A5%E5%8F%A3.html" class="nav-link sidebar-link sidebar-page" aria-label="第 5 章 类和接口"><!---->第 5 章 类和接口<!----></a><ul class="sidebar-sub-headers"></ul><!--]--></li><li><!--[--><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html" class="router-link-active router-link-exact-active nav-link active sidebar-link sidebar-page active" aria-label="第 6 章 类型进阶"><!---->第 6 章 类型进阶<!----></a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-类型之间的关系" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.1 类型之间的关系"><!---->6.1 类型之间的关系<!----></a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-1-子类型和超类型" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.1.1 子类型和超类型"><!---->6.1.1 子类型和超类型<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-2-型变" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.1.2 型变"><!---->6.1.2 型变<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-3-可赋值性" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.1.3 可赋值性"><!---->6.1.3 可赋值性<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-4-类型拓宽" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.1.4 类型拓宽"><!---->6.1.4 类型拓宽<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-5-细化" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.1.5 细化"><!---->6.1.5 细化<!----></a><ul class="sidebar-sub-headers"></ul></li></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-2-全面性检查" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.2 全面性检查"><!---->6.2 全面性检查<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-对象类型进阶" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.3 对象类型进阶"><!---->6.3 对象类型进阶<!----></a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-1-对象类型的类型运算" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.3.1 对象类型的类型运算"><!---->6.3.1 对象类型的类型运算<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-2-record-类型" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.3.2 Record 类型"><!---->6.3.2 Record 类型<!----></a><ul class="sidebar-sub-headers"></ul></li><li class="sidebar-sub-header"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-3-映射类型" class="router-link-active router-link-exact-active nav-link sidebar-link heading" aria-label="6.3.3 映射类型"><!---->6.3.3 映射类型<!----></a><ul class="sidebar-sub-headers"></ul></li></ul></li></ul><!--]--></li></ul></section></li><li><section class="sidebar-group"><button class="sidebar-heading clickable"><!----><span class="title">《Vue.js 设计与实现》</span><span class="arrow right"></span></button><!----></section></li></ul><!--[--><!----><!--]--></aside><!--[--><main class="page" id="main-content"><!--[--><!----><nav class="breadcrumb disable"></nav><div class="page-title"><h1><!---->第 6 章 类型进阶</h1><div class="page-info"><!----><!----><!----><!----><!----><span class="reading-time-info" aria-label="阅读时间⌛" data-balloon-pos="down"><svg xmlns="http://www.w3.org/2000/svg" class="icon timer-icon" viewBox="0 0 1024 1024" fill="currentColor" aria-label="timer icon"><path d="M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"></path></svg><span>大约 16 分钟</span><meta property="timeRequired" content="PT16M"></span></div><hr></div><div class="toc-place-holder"><aside id="toc"><div class="toc-header">此页内容</div><div class="toc-wrapper"><ul class="toc-list"><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-类型之间的关系" class="router-link-active router-link-exact-active toc-link level2">6.1 类型之间的关系</a></li><ul class="toc-list"><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-1-子类型和超类型" class="router-link-active router-link-exact-active toc-link level3">6.1.1 子类型和超类型</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-2-型变" class="router-link-active router-link-exact-active toc-link level3">6.1.2 型变</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-3-可赋值性" class="router-link-active router-link-exact-active toc-link level3">6.1.3 可赋值性</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-4-类型拓宽" class="router-link-active router-link-exact-active toc-link level3">6.1.4 类型拓宽</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-1-5-细化" class="router-link-active router-link-exact-active toc-link level3">6.1.5 细化</a></li><!----><!--]--></ul><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-2-全面性检查" class="router-link-active router-link-exact-active toc-link level2">6.2 全面性检查</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-对象类型进阶" class="router-link-active router-link-exact-active toc-link level2">6.3 对象类型进阶</a></li><ul class="toc-list"><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-1-对象类型的类型运算" class="router-link-active router-link-exact-active toc-link level3">6.3.1 对象类型的类型运算</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-2-record-类型" class="router-link-active router-link-exact-active toc-link level3">6.3.2 Record 类型</a></li><!----><!--]--><!--[--><li class="toc-item"><a aria-current="page" href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%206%20%E7%AB%A0%20%E7%B1%BB%E5%9E%8B%E8%BF%9B%E9%98%B6.html#_6-3-3-映射类型" class="router-link-active router-link-exact-active toc-link level3">6.3.3 映射类型</a></li><!----><!--]--></ul><!--]--></ul></div></aside></div><!----><div class="theme-hope-content"><h1 id="第-6-章-类型进阶" tabindex="-1"><a class="header-anchor" href="#第-6-章-类型进阶" aria-hidden="true">#</a> 第 6 章 类型进阶</h1><h2 id="_6-1-类型之间的关系" tabindex="-1"><a class="header-anchor" href="#_6-1-类型之间的关系" aria-hidden="true">#</a> 6.1 类型之间的关系</h2><h3 id="_6-1-1-子类型和超类型" tabindex="-1"><a class="header-anchor" href="#_6-1-1-子类型和超类型" aria-hidden="true">#</a> 6.1.1 子类型和超类型</h3><p>3.1 节简单讲过可赋值性。</p><blockquote><p>子类型：给定两个类型 A 和 B，假设 B 是 A 的子类型，那么在需要 A 的地方都可以使用 B。</p></blockquote><p>第三章开头的图可以看出 TypeScript 各个类型之间的关系：</p><ul><li>Array 是 Object 的子类型</li><li>Tuple 是 Array 的子类型</li><li>所有类型都是 any 的子类型</li><li>never 是所有类型的子类型</li><li>如果 Bird 继承自 Animal 类，那么 Bird 是 Animal 的子类型</li></ul><p>根据上述定义：</p><ul><li>需要使用 Object 的地方都可以使用 Array</li><li>需要 Array 的地方都可以使用 Tuple</li><li>需要 any 的地方都可以使用任意类型</li><li>never 可以再任何地方使用</li><li>需要 Animal 的地方都可以使用 Bird</li></ul><p>超类型正好与子类型相反：</p><blockquote><p>超类型：给定两个类型 A 和 B，假设 B 是 A 的超类型，那么在需要 B 的地方都可以使用 A。</p></blockquote><ul><li>Array 是 Tuple 的超类</li><li>Object 是 Array 的超类</li><li>any 是所有类型的超类</li><li>never 不是任何类型的超类</li><li>Animal 是 Bird 的超类</li></ul><h3 id="_6-1-2-型变" tabindex="-1"><a class="header-anchor" href="#_6-1-2-型变" aria-hidden="true">#</a> 6.1.2 型变</h3><p>多数情况下，很容易判断 A 类型是是不是 B 类型的子类型。但是对于参数化类型（泛型）和其他较为复杂的类型来说，不是很明晰：</p><ul><li>什么情况下 Array&lt;A&gt; 是 Array&lt;B&gt; 的子类型</li><li>什么情况下结构 A 是结构 B 的子类型</li><li>什么情况下函数(a:A)=&gt; B 是 函数 (c:C)=&gt; D 的子类型</li></ul><p>如果一个类型中包含其他类型（即带有类型参数的类型，如 Array&lt;A&gt;；带有字段的结构，如{a:number}；或者函数，如(a:A)=&gt; B），使用上述规则很难判断谁是子类型。</p><p>以下将会使用：</p><ul><li><strong>A &lt;: B 指 “类型 A 是类型 B 的子类型，或者为同种类型”</strong></li><li><strong>A &gt;: B 指 “类型 A 是类型 B 的超类型，或者为同种类型”</strong></li></ul><h4 id="结构和数组型变" tabindex="-1"><a class="header-anchor" href="#结构和数组型变" aria-hidden="true">#</a> 结构和数组型变</h4><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">ExistingUser</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">type</span> <span class="token class-name">NewUser</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">deleteUser</span><span class="token punctuation">(</span>user<span class="token operator">:</span> <span class="token punctuation">{</span> id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> name<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">delete</span> user<span class="token punctuation">.</span>id<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">let</span> existingUser<span class="token operator">:</span> ExistingUser <span class="token operator">=</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token number">12345</span><span class="token punctuation">,</span>
  name<span class="token operator">:</span> <span class="token string">&quot;Bob&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">deleteUser</span><span class="token punctuation">(</span>existingUser<span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上述例子中：deleteUser 接受一个对象，类型为 <code>{id?:number; name:string}</code>,传入的 existingUser 类型为 <code>{id:number; name:string}</code>。id 的类型（number）是预期类型（number|undefined）的子类型。因此，<code>{id?:number; name:string}</code> 作为一个整体是 <code>{id:number; name:string}</code> 的子类型，所以 TypeScript 不会报错。</p><p>但是把 ExistingUser 类型值传给 deleteUser 后，TypeScript 不知道用户的 id 已经被删除，所以调用 deleteUser 把 id 属性删除之后 再读取 <code>existingUser.id</code>，TypeScript 仍认为 id 为 number 类型，此时 id 应为 undefined。</p><p>在预期某个超类型的地方使用该类型是不安全的。在实际操作中少见，所以 TypeScript 放宽检测要求，允许使用。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">LegacyUser</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">let</span> legacyUser<span class="token operator">:</span> LegacyUser <span class="token operator">=</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token string">&quot;123&quot;</span><span class="token punctuation">,</span>
  name<span class="token operator">:</span> <span class="token string">&quot;Jany&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">deleteUser</span><span class="token punctuation">(</span>legacyUser<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 错误提示：</span>
<span class="token comment">// 类型“LegacyUser”的参数不能赋给类型“{ id?: number | undefined; name: string; }”的参数。</span>
<span class="token comment">//   属性“id”的类型不兼容。</span>
<span class="token comment">//     不能将类型“string | number | undefined”分配给类型“number | undefined”。</span>
<span class="token comment">//       不能将类型“string”分配给类型“number”。</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>TypeScript 中，对预期的结构，还可以使用属性的类型 &lt;: 预期类型结构（使用的类型是预期类型的子类型），但是不能传入属性的类型是预期类型的超类型结构。预期类，例如：<strong>函数的参数、泛型的类型</strong>。</p><p>在类型上，可以说 TypeScript 对结构（对象和类）的属性进行<strong>协变（covariant）</strong>。也就是说，如果想保证 A 对象可赋值给 B 对象，那么 A 对象的每个属性都必须 &lt;: B对象的对应属性。</p><p>协变只是型变的四种方式之一：</p><ol><li>不变 <ul><li>只能是 T</li></ul></li><li>协变 <ul><li>可以是 &lt;: T</li></ul></li><li>逆变 <ul><li>可以是 &gt;: T</li></ul></li><li>双变 <ul><li>可以是 &lt;:T 或 &gt;:T</li></ul></li></ol><p>在 TypeScript 中，每个复杂类型的成员都会进行协变，包括对象、类、数组和函数的返回类型。有个例外：函数的参数类型进行逆变</p><div class="custom-container info"><p class="custom-container-title">相关信息</p><p>不是所有编程语言都采用这种方式。在某些语言中，对象的属性类型&quot;不变&quot;，因为，协变属性的类型可能导致不安全的行为。</p></div><h4 id="函数型变" tabindex="-1"><a class="header-anchor" href="#函数型变" aria-hidden="true">#</a> 函数型变</h4><p>如果函数 A 的参数数量小于或等于函数 B 的参数数量，而且满足下述条件，那么函数 A 是函数 B 的子类型：</p><ol><li><strong>函数 A 的 this 类型未定义，或 &gt;: 函数 B 的 this 类型</strong></li><li><strong>函数 A 的各个参数类型 &gt;: 函数 B 的对应参数类型</strong></li><li><strong>函数 A 的返回类型 &lt;: 函数 B 的返回类型</strong></li></ol><p>注意上述第 1 和 第 3 条,函数并不像对象、数组、并集类型那样，每一部分（this 类型、参数的类型和返回类型）都是 &lt;:，下面将会验证：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">class</span> <span class="token class-name">Animal</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">Bird</span> <span class="token keyword">extends</span> <span class="token class-name">Animal</span> <span class="token punctuation">{</span>
  <span class="token function">chirp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">Crow</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span>
  <span class="token function">caw</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">chirp</span><span class="token punctuation">(</span>bird<span class="token operator">:</span> Bird<span class="token punctuation">)</span><span class="token operator">:</span> Bird <span class="token punctuation">{</span>
  bird<span class="token punctuation">.</span><span class="token function">chirp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> bird<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token function">chirp</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Animal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">chirp</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">chirp</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Crow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上述代码中，<code>chirp(new Animal())</code> 会提示类型 Animal 上不存在 chirp 属性，因为 Bird 是 Animal 的子类型，而 chirp 只存在 Bird 类上。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">function</span> <span class="token function">clone</span><span class="token punctuation">(</span><span class="token function-variable function">f</span><span class="token operator">:</span> <span class="token punctuation">(</span>b<span class="token operator">:</span> Bird<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Bird<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">birdToBird</span><span class="token punctuation">(</span>b<span class="token operator">:</span> Bird<span class="token punctuation">)</span><span class="token operator">:</span> Bird <span class="token punctuation">{</span>
  <span class="token keyword">return</span> b<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">clone</span><span class="token punctuation">(</span>birdToBird<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">birdToCrow</span><span class="token punctuation">(</span>d<span class="token operator">:</span> Bird<span class="token punctuation">)</span><span class="token operator">:</span> Crow <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token function">clone</span><span class="token punctuation">(</span>birdToCrow<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">birdToAnimal</span><span class="token punctuation">(</span>d<span class="token operator">:</span> Bird<span class="token punctuation">)</span><span class="token operator">:</span> Animal <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token function">clone</span><span class="token punctuation">(</span>birdToAnimal<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 类型“(d: Bird) =&gt; Animal”的参数不能赋给类型“(b: Bird) =&gt; Bird”的参数。</span>
<span class="token comment">//   不能将类型“Animal”分配给类型“Bird”。</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>birdToCrow 可按照预期调用，birdToAnimal 会报错。如果 clone 代码如下：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">function</span> <span class="token function">clone</span><span class="token punctuation">(</span><span class="token function-variable function">f</span><span class="token operator">:</span> <span class="token punctuation">(</span>b<span class="token operator">:</span> Bird<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> Bird<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> parent <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> babyBird <span class="token operator">=</span> <span class="token function">f</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span>
  babyBird<span class="token punctuation">.</span><span class="token function">chirp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此时调用了 Bird 实例的 chirp 方法，如果 f 传入的是 Animal 的实例则不能调用 chirp 方法。在编译时 TypeScript 会确保传入的函数至少返回一个 Bird。</p><p>函数返回类型的协变指一个函数是另一个函数的子类型，即一个函数的返回类型 &lt;: 另一个函数的返回类型。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">function</span> <span class="token function">animalToBird</span><span class="token punctuation">(</span>a<span class="token operator">:</span> Animal<span class="token punctuation">)</span><span class="token operator">:</span> Bird <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token function">clone</span><span class="token punctuation">(</span>animalToBird<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">corwToBird</span><span class="token punctuation">(</span>c<span class="token operator">:</span> Crow<span class="token punctuation">)</span><span class="token operator">:</span> Bird <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token function">clone</span><span class="token punctuation">(</span>corwToBird<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 类型“(c: Crow) =&gt; Bird”的参数不能赋给类型“(b: Bird) =&gt; Bird”的参数。</span>
<span class="token comment">//   参数“c”和“b” 的类型不兼容。</span>
<span class="token comment">//     类型 &quot;Bird&quot; 中缺少属性 &quot;caw&quot;，但类型 &quot;Crow&quot; 中需要该属性。</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>参数类型：为了保证一个函数可赋值给另一个函数，该函数的参数类型（包括 this）都要 &gt;: 另一个函数相应的参数类型。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">function</span> <span class="token function">crowToBird</span><span class="token punctuation">(</span>c<span class="token operator">:</span> Crow<span class="token punctuation">)</span><span class="token operator">:</span> Bird <span class="token punctuation">{</span>
  c<span class="token punctuation">.</span><span class="token function">caw</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>crowToBird</code> 中使用到 caw 方法，clone 的参数类型是 <code>(b: Bird) =&gt; Bird</code>，Bird 是 Crow 的超类型，caw 方法只在 Crow 上定义。</p><p>表名，函数不对参数和 this 的类型做型变。也就是说，一个函数是另一个函数的子类型，必须保证该函数的参数和 this 的类型 &gt;: 另一个函数的相应参数的类型。</p><div class="custom-container tip"><p class="custom-container-title">TSC 标志：strictFunctionTypes</p><p>考虑历史遗留问题，TypeScript 中的函数其实会默认对参数和 this 的类型做协变。如果更想安全，不做型变，在 tsconfig.json 中配置 <code>&quot;strictFunctionTypes&quot;:true</code></p><p>strict 模式包含 strictFunctionTypes</p></div><h3 id="_6-1-3-可赋值性" tabindex="-1"><a class="header-anchor" href="#_6-1-3-可赋值性" aria-hidden="true">#</a> 6.1.3 可赋值性</h3><p>子类型和超类型关系是静态类型语言的核心，对理解可赋值性也十分重要（可赋值性值在判断需要 B 类型的地方是否可使用 A 类型）</p><p>TypeScript 在处理赋值时遵循如下规则：</p><p>枚举类型，例如数组、布尔值、数字、对象、函数、类、类的实例和字符串，以及字面量类型，满足下述任一条件即可 A 类型 赋值给 B 类型：</p><ol><li>A &lt;: B 即 A 类型是 B 类型的子类型</li><li>A 是 any</li></ol><p>规则 1 就是子类型的定义：若果 A 类型是 B 类型的子类型，那么需要 B 的地方也可以使用 A。规则 2 是规则 1 的例外，为了方便 JavaScript 代码互操作。</p><p>对于使用 enum 或 const enum 关键字创建的枚举类型，满足以下任一条件，A 类型可赋值给 B 类型：</p><ol><li>A 是枚举 B 的成员</li><li>B 至少有一个成员是 number 类型，而且 A 是数字。</li></ol><p>规则 1 与简单类型的规则完全一样（如果 A 是枚举 B 的成员，那么 A 的类型是 B，也就是 A &lt;: B）。规则 2 是为了方便使用枚举，存在安全隐患。</p><h3 id="_6-1-4-类型拓宽" tabindex="-1"><a class="header-anchor" href="#_6-1-4-类型拓宽" aria-hidden="true">#</a> 6.1.4 类型拓宽</h3><p>类型拓宽（type widening） 是理解 TypeScript 类型推导机制的关键。一般来说，TypeScript 在推导类型时会放宽要求，故意推导出一个更宽泛的类型，而不限定为某个具体类型。 这样做大大减少消除了类型检查器报错的时间。</p><p>声明变量时如果允许以后修改变量的值（如使用 let 或 var 声明），变量的类型将拓宽，从字面值放大到包含该字面量的基类型。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token string">&quot;x&quot;</span><span class="token punctuation">;</span> <span class="token comment">// string</span>
<span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment">// number</span>
<span class="token keyword">var</span> c <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment">// boolean</span>
<span class="token keyword">const</span> d <span class="token operator">=</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// {x:number}</span>

<span class="token keyword">enum</span> <span class="token constant">E</span> <span class="token punctuation">{</span>
  <span class="token constant">X</span><span class="token punctuation">,</span>
  <span class="token constant">Y</span><span class="token punctuation">,</span>
  <span class="token constant">Z</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>

<span class="token keyword">let</span> e <span class="token operator">=</span> <span class="token constant">E</span><span class="token punctuation">.</span><span class="token constant">X</span><span class="token punctuation">;</span> <span class="token comment">// E</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>声明不可变的变量时：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">const</span> a <span class="token operator">=</span> <span class="token string">&quot;x&quot;</span><span class="token punctuation">;</span> <span class="token comment">// x</span>
<span class="token keyword">const</span> b <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>
<span class="token keyword">const</span> c <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment">// true</span>
<span class="token keyword">enum</span> <span class="token constant">E</span> <span class="token punctuation">{</span>
  <span class="token constant">X</span><span class="token punctuation">,</span>
  <span class="token constant">Y</span><span class="token punctuation">,</span>
  <span class="token constant">Z</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">const</span> e <span class="token operator">=</span> <span class="token constant">E</span><span class="token punctuation">.</span><span class="token constant">X</span><span class="token punctuation">;</span> <span class="token comment">// E.X</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以显示注解类型，防止类型被拓宽：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">let</span> a<span class="token operator">:</span> <span class="token string">&quot;x&quot;</span> <span class="token operator">=</span> <span class="token string">&quot;x&quot;</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> b<span class="token operator">:</span> <span class="token number">3</span> <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> c<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> d<span class="token operator">:</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果使用 let 或 var 重新为非拓宽类型赋值，TypeScript 将自动拓宽。倘若不想让 TypeScript 拓宽，一开始声明时要显示注解类型：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">const</span> a <span class="token operator">=</span> <span class="token string">&quot;x&quot;</span><span class="token punctuation">;</span> <span class="token comment">// &#39;x&#39;</span>
<span class="token keyword">let</span> b <span class="token operator">=</span> a<span class="token punctuation">;</span> <span class="token comment">// string</span>
<span class="token keyword">const</span> c<span class="token operator">:</span> <span class="token string">&quot;x&quot;</span> <span class="token operator">=</span> <span class="token string">&quot;x&quot;</span><span class="token punctuation">;</span> <span class="token comment">// &#39;x&#39;</span>
<span class="token keyword">let</span> d <span class="token operator">=</span> c<span class="token punctuation">;</span> <span class="token comment">// &#39;x&#39;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>初始化为 null 或 undefined 的变量将拓宽为 any:</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">let</span> e <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// any</span>
e <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment">// any</span>
e <span class="token operator">=</span> <span class="token string">&quot;b&quot;</span><span class="token punctuation">;</span> <span class="token comment">// any</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>但是，当初始化为 null 或 undefined 的变量离开声明时所在的作用域后，TypeScript 将其分配一个具体类型：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">function</span> <span class="token function">x</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// any</span>
  a <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment">// any</span>
  a <span class="token operator">=</span> <span class="token string">&quot;b&quot;</span><span class="token punctuation">;</span> <span class="token comment">// any</span>
  <span class="token keyword">return</span> a<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">x</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// string</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="const-类型" tabindex="-1"><a class="header-anchor" href="#const-类型" aria-hidden="true">#</a> const 类型</h4><p>TypeScript 中有个特殊的 const 类型，可以在单个声明中使用它禁止类型拓宽。这个类型用做类型断言。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// {x:number}</span>
<span class="token keyword">let</span> b<span class="token operator">:</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// {x:3}</span>
<span class="token keyword">const</span> c <span class="token operator">=</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">;</span> <span class="token comment">// { readonly x: 3}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>const 不仅能阻止拓宽类型，还将递归把成员设为 readonly，不管数据结构嵌套的有多深：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">let</span> d <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> b<span class="token operator">:</span> <span class="token punctuation">{</span> d<span class="token operator">:</span> <span class="token number">5</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">//  d: (number | {x: number;b: {d: number}})[]</span>

<span class="token keyword">let</span> e <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> x<span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> b<span class="token operator">:</span> <span class="token punctuation">{</span> d<span class="token operator">:</span> <span class="token number">5</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">]</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">;</span>
<span class="token comment">// readonly [1, {</span>
<span class="token comment">//     readonly x: 3;</span>
<span class="token comment">//     readonly b: {</span>
<span class="token comment">//         readonly d: 5;</span>
<span class="token comment">//     };</span>
<span class="token comment">// }]</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果想让 TypeScript 推导类型尽量窄一些，使用 as const。</p><h4 id="多余属性检查" tabindex="-1"><a class="header-anchor" href="#多余属性检查" aria-hidden="true">#</a> 多余属性检查</h4><p>TypeScript 检查一个对象是否可赋值给另一个对象类型时，也涉及到类型拓宽。对象类型的成员会做协变。</p><p>如下过程：</p><ul><li>预期类型是 <code>{baseURL:string,cacheSize?:number,tier?:&#39;prd&#39;|&#39;dev&#39;}</code></li><li>传入的类型是 <code>{baseURL:string,tierr:string}</code></li><li>传入的类型是预期类型的子类型，不知为何，TypeScript 知道要报错。</li></ul><p>TypeScript 之所以能捕获这样错误，因为它会做多余属性检查，具体是：尝试把一个新鲜对象字面量类型（fresh object literal type）T 赋值给另一个类型 U 时，如果 T 有不在 U 中的属性，TypeScript 将报错。</p><p>新鲜对象字面量类型指的是 TypeScript 从对象字面量中推导出来的类型。如果对象字面量有类型断言，或者把对象字面量赋值给变量，那么新鲜对象字面量类型将拓宽为常规的对象类型，也就不能称其为新鲜对象字面量类型。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">Options</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  baseURL<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
  cacheSize<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  tier<span class="token operator">?</span><span class="token operator">:</span> <span class="token string">&quot;prd&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;dev&quot;</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">class</span> <span class="token class-name"><span class="token constant">API</span></span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> options<span class="token operator">:</span> Options<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 1</span>
<span class="token keyword">new</span> <span class="token class-name"><span class="token constant">API</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  baseURL<span class="token operator">:</span> <span class="token string">&quot;ssss&quot;</span><span class="token punctuation">,</span>
  tier<span class="token operator">:</span> <span class="token string">&quot;dev&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 2</span>
<span class="token keyword">new</span> <span class="token class-name"><span class="token constant">API</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  baseURL<span class="token operator">:</span> <span class="token string">&quot;xxx&quot;</span><span class="token punctuation">,</span>
  tieer<span class="token operator">:</span> <span class="token string">&quot;xxx&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 3</span>
<span class="token keyword">new</span> <span class="token class-name"><span class="token constant">API</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  baseURL<span class="token operator">:</span> <span class="token string">&quot;XXX&quot;</span><span class="token punctuation">,</span>
  tiert<span class="token operator">:</span> <span class="token string">&quot;dev&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">as</span> Options<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">let</span> o <span class="token operator">=</span> <span class="token punctuation">{</span>
  baseURL<span class="token operator">:</span> <span class="token string">&quot;XXX&quot;</span><span class="token punctuation">,</span>
  tiert<span class="token operator">:</span> <span class="token string">&quot;dev&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">new</span> <span class="token class-name"><span class="token constant">API</span></span><span class="token punctuation">(</span>o<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 4</span>
<span class="token keyword">let</span> o<span class="token operator">:</span> Options <span class="token operator">=</span> <span class="token punctuation">{</span>
  baseURL<span class="token operator">:</span> <span class="token string">&quot;XXX&quot;</span><span class="token punctuation">,</span>
  tiert<span class="token operator">:</span> <span class="token string">&quot;dev&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">new</span> <span class="token class-name"><span class="token constant">API</span></span><span class="token punctuation">(</span>o<span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol><li>使用 baseURL 和这两个可选属性中的 tier 实例化 API</li><li>错误传入 tieer。传给 new API 的是新鲜的选项对象（因为其类型是推导出来的，没有赋值给变量，也没有对类型下断言）。因此 TypeScript 将做多余属性检查，发现 tieer 属性是多余的。</li><li>断言传入的无效选项对象是 Options 类型。TypeScript 不在把它视作新鲜对象，因此不做多余属性检查，所以不报错。</li><li>把选项对象赋值给变量 o。TypeScript 不再把它视作新鲜对象，因此不做对于属性检查，所以不报错。</li><li>显示注解 o 的类型为 Options，赋值给 o 的是一个新鲜对象，所以 TypeScript 执行多与属性检查，捕获存在的问题。这种情况下，TypeScript 不在把 options 传给 new API 时做多余属性检查，而是在把选项对象赋值给变量 o 时检查。</li></ol><h3 id="_6-1-5-细化" tabindex="-1"><a class="header-anchor" href="#_6-1-5-细化" aria-hidden="true">#</a> 6.1.5 细化</h3><p>TypeScript 采用的是基于流的类型推导，这是一种符号执行，类型检查器在检查代码的过程中利用流程语句（如 if、?、|| 或 switch）和类型查询（如：typeof、instanceof 和 in）细化类型。</p><div class="custom-container tip"><p class="custom-container-title">提示</p><p>符号执行是一种分析程序的方式，这种方式使用一个特殊的程序（称为符号求值程序）运行程序，过程与运行时运行程序一样，只是不为变量赋予具体的值，而使用符号建模变量，在程序运行的过程中约束变量的值。符号执行可以表达“这个变量从未使用” “这个函数永不返回”，或者 “在 if 语句中肯定分支中，第 102 行肯定不是 null”</p></div><p>分析示例：使用 TypeScript 构建一个定义 CSS 规则的 API。用该 API 为一个 HTML 元素设置 width。这个 API 要对传入的的宽度做解析和验证。</p><p>首先，实现一个函数，把 CSS 字符串中的值和单位解析出来。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token comment">// 使用字符串字面量的并集描述 CSS 单位的可取值</span>
<span class="token keyword">type</span> <span class="token class-name">Unit</span> <span class="token operator">=</span> <span class="token string">&quot;cm&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;px&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;%&quot;</span><span class="token punctuation">;</span>
<span class="token comment">// 列举单位</span>
<span class="token keyword">let</span> units<span class="token operator">:</span> Unit<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">&quot;cm&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;px&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;%&quot;</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token comment">// 检查各个单位，如果没有匹配到，返回 null</span>
<span class="token keyword">function</span> <span class="token function">parseUnit</span><span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> Unit <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token punctuation">{</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> units<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span>units<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> units<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以使用 parseUnit 函数解析用户传入的宽度值。width 的值可能是一个数字（此时假定单位为像素），可能是一个带单位的字符串，也可能是 null 或 undefined。</p><p>接上述代码细化：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code>
<span class="token keyword">type</span> <span class="token class-name">Width</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  unit<span class="token operator">:</span> Unit<span class="token punctuation">;</span>
  value<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">parseWidth</span><span class="token punctuation">(</span>width<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">|</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token operator">:</span> Width <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token punctuation">{</span>
  <span class="token comment">// 如果 width 是 null 或 undefined 返回 null</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>width <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> width <span class="token operator">===</span> <span class="token string">&quot;number&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span> unit<span class="token operator">:</span> <span class="token string">&quot;px&quot;</span><span class="token punctuation">,</span> value<span class="token operator">:</span> width <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">let</span> unit <span class="token operator">=</span> <span class="token function">parseUnit</span><span class="token punctuation">(</span>width<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>unit<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      unit<span class="token punctuation">,</span>
      value<span class="token operator">:</span> <span class="token function">parseFloat</span><span class="token punctuation">(</span>width<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="custom-container tip"><p class="custom-container-title">提示</p><p>JavaScript 有七个假值：null,undefined,NaN,0,-0,&quot;&quot; 和 false。除此之外均为真值。</p></div><h4 id="辨别并集类型" tabindex="-1"><a class="header-anchor" href="#辨别并集类型" aria-hidden="true">#</a> 辨别并集类型</h4><p>由于并集类型的成员有可能重复，因此 TypeScript 需要一种更可靠的方式，明确并集类型的具体情况。</p><p>为此，要使用一个字面量类型标记并集类型的各种情况。一个好的标记要满足以下条件：</p><ul><li>在并集类型各组成部分的相同位置上。如果是对象类型的并集，使用相同的字段；如果是元组类型的并集，使用相同的索引。实际使用中，带标记的并集类型通常为对象类型。</li><li>使用字面量类型（字符串、数字、布尔值等字面量）。可以混用不同的字面量类型，不过最好使用同一种类型。通常，使用字符串字面量类型。</li><li>不要使用泛型。标记不应该有任何泛型参数。</li><li>要互斥（即在并集类型中独一无二）</li></ul><h2 id="_6-2-全面性检查" tabindex="-1"><a class="header-anchor" href="#_6-2-全面性检查" aria-hidden="true">#</a> 6.2 全面性检查</h2><p>全面性检查（也称穷尽性检查）是类型检查器所做的一项检查，为的是确保所有情况都被覆盖了。</p><p>TypeScript 在很多情况下都会做全面性检查，如果发现缺少某种情况会提示。</p><p>例如：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">WeekDay</span> <span class="token operator">=</span> <span class="token string">&quot;Mon&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;Tue&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;Wed&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;Thu&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;Fri&quot;</span><span class="token punctuation">;</span>
<span class="token keyword">type</span> <span class="token class-name">Day</span> <span class="token operator">=</span> WeekDay <span class="token operator">|</span> <span class="token string">&quot;Sat&quot;</span> <span class="token operator">|</span> <span class="token string">&quot;Sun&quot;</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">getNextDay</span><span class="token punctuation">(</span>w<span class="token operator">:</span> WeekDay<span class="token punctuation">)</span><span class="token operator">:</span> Day <span class="token punctuation">{</span>
  <span class="token keyword">switch</span> <span class="token punctuation">(</span>w<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">case</span> <span class="token string">&quot;Mon&quot;</span><span class="token operator">:</span>
      <span class="token keyword">return</span> <span class="token string">&quot;Tue&quot;</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>遗漏了好几天。TypeScript 会捕获问题。</p><div class="custom-container tip"><p class="custom-container-title">TSC 标志：noImplicitReturns</p><p>如果想让 TypeScript 检查函数的所有代码路径都有返回值（在缺少返回值是抛出错误），在 tsconfig.json 中启用 noImplicitReturns 标志。</p></div><p>上述情况可以在最后加一个兜底的 return 语句，也可以调整 getNextDay 返回类型为 <code>Day|nudefined</code>。</p><h2 id="_6-3-对象类型进阶" tabindex="-1"><a class="header-anchor" href="#_6-3-对象类型进阶" aria-hidden="true">#</a> 6.3 对象类型进阶</h2><h3 id="_6-3-1-对象类型的类型运算" tabindex="-1"><a class="header-anchor" href="#_6-3-1-对象类型的类型运算" aria-hidden="true">#</a> 6.3.1 对象类型的类型运算</h3><h4 id="键入-运算符" tabindex="-1"><a class="header-anchor" href="#键入-运算符" aria-hidden="true">#</a> &quot;键入&quot;运算符</h4><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">ApiResponse</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  useId<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  friends<span class="token operator">:</span> <span class="token punctuation">{</span>
    count<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
    list<span class="token operator">:</span> <span class="token punctuation">{</span>
      firstName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
      lastName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token doc-comment comment">/**
 * 此时 friends 的类型和 ApiResponse 中 friends 的类型一样
 * 可以使用 “键入”
 */</span>
<span class="token keyword">type</span> <span class="token class-name">Friends</span> <span class="token operator">=</span> ApiResponse<span class="token punctuation">[</span><span class="token string">&quot;friends&quot;</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>任何结构（对象，类构造方法或类的实例）和数组都可以“键入”。</p><p>“键入”的语法与在 JavaScript 对象中查找字段的句法类似。通过“键入”查找属性类型时，只能使用<code>[]</code> 表示。</p><h4 id="keyof-运算符" tabindex="-1"><a class="header-anchor" href="#keyof-运算符" aria-hidden="true">#</a> keyof 运算符</h4><p>keyof 运算符获取对象所有键的类型，合并为一个字符串字面量类型。</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">ApiResponseKeys</span> <span class="token operator">=</span> <span class="token keyword">keyof</span> ApiResponse<span class="token punctuation">;</span> <span class="token comment">// userId | friends</span>
<span class="token keyword">type</span> <span class="token class-name">friendsKeys</span> <span class="token operator">=</span> <span class="token keyword">keyof</span> ApiResponse<span class="token punctuation">[</span><span class="token string">&quot;friends&quot;</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// count | list</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><div class="custom-container tip"><p class="custom-container-title">TSC 标志：keyofStringOnly</p><p>在 JavaScript 中，对象和数组的键可以使用字符串，也可以使用符号。数组通常使用数字键，但在运行时将被强制转换为字符串。</p><p>TypeScript 中的 keyof 运算符默认返回 number | string | symbol 类型。</p><p>如果想要求键必须为字符串可开启该标志。</p></div><h3 id="_6-3-2-record-类型" tabindex="-1"><a class="header-anchor" href="#_6-3-2-record-类型" aria-hidden="true">#</a> 6.3.2 Record 类型</h3><p>TypeScript 内置 Record 类型用于描述有映射关系的对象。</p><p>与常规的对象索引签名相比，Record 使用常规的索引签名可以约束对象中的值类型，不过键只能用 string、number 或 symbol 类型，使用 Record 还可以约束对象的键为 string 和 number 的子类型。</p><h3 id="_6-3-3-映射类型" tabindex="-1"><a class="header-anchor" href="#_6-3-3-映射类型" aria-hidden="true">#</a> 6.3.3 映射类型</h3><p>映射类型（mapped type）是 TypeScript 独有的语言特性。为 JavaScript 弥补了静态类型。</p><p>映射类型与索引签名相同，一个对象最多有一个映射类型：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">MyMappedType</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token punctuation">[</span>Key <span class="token keyword">in</span> UnionType<span class="token punctuation">]</span><span class="token operator">:</span> ValueType<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>TypeScript 内置的 Record 类型也是使用映射类型实现的：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">Record<span class="token operator">&lt;</span><span class="token constant">K</span> <span class="token keyword">extends</span> <span class="token keyword">keyof</span> <span class="token builtin">any</span><span class="token punctuation">,</span> <span class="token constant">T</span><span class="token operator">&gt;</span></span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token punctuation">[</span><span class="token constant">P</span> <span class="token keyword">in</span> <span class="token constant">K</span><span class="token punctuation">]</span><span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>映射类型功能比 Record 强大，在指定对象的键和值的类型以外，如果结合 “键入” 类型，还能约束特定键的值是什么类型。</p><p>映射类型可以做哪些事情：</p><div class="language-typescript line-numbers-mode" data-ext="ts"><pre class="language-typescript"><code><span class="token keyword">type</span> <span class="token class-name">Account</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  id<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  isEmployee<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span>
  notes<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token doc-comment comment">/**
 * 所有字段都是可选的
 * 
 * 新建对象类型 OptionalAccount，与 Account 建立映射，在此过程
 * 中把各个字段标记为可选的
 */</span>
<span class="token keyword">type</span> <span class="token class-name">OptionalAccount</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token punctuation">[</span><span class="token constant">K</span> <span class="token keyword">in</span> <span class="token keyword">keyof</span> Account<span class="token punctuation">]</span><span class="token operator">?</span><span class="token operator">:</span> Account<span class="token punctuation">[</span><span class="token constant">K</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>


<span class="token doc-comment comment">/**
 * 所有字段都可为 null
 * 
 * 新建对象类型 NullableAccount，与 Account 建立映射，在此过程
 * 中为每个字段增加可选值 null
 */</span>
<span class="token keyword">type</span> <span class="token class-name">NullableAccount</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token punctuation">[</span><span class="token constant">K</span> <span class="token keyword">in</span> <span class="token keyword">keyof</span> Account<span class="token punctuation">]</span><span class="token operator">?</span><span class="token operator">:</span> Account<span class="token punctuation">[</span><span class="token constant">K</span><span class="token punctuation">]</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>


<span class="token doc-comment comment">/**
 * 所有字段都只读
 * 
 * 新建对象类型 ReadonlyAccount，与 Account 建立映射，把个字段标记为只读
 */</span>
<span class="token keyword">type</span> <span class="token class-name">ReadonlyAccount</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token keyword">readonly</span> <span class="token punctuation">[</span><span class="token constant">K</span> <span class="token keyword">in</span> <span class="token keyword">keyof</span> Account<span class="token punctuation">]</span><span class="token operator">:</span> Account<span class="token punctuation">[</span><span class="token constant">K</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// 所有字段都是可写的（等同于 Account）</span>
<span class="token keyword">type</span> <span class="token class-name">AccountCopy</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token operator">-</span><span class="token keyword">readonly</span> <span class="token punctuation">[</span><span class="token constant">K</span> <span class="token keyword">in</span> <span class="token keyword">keyof</span> Account<span class="token punctuation">]</span><span class="token operator">:</span> Account<span class="token punctuation">[</span><span class="token constant">K</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// 所有字段都是必须的（等同于 Account）</span>
<span class="token keyword">type</span> <span class="token class-name">AccountCopy2</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token punctuation">[</span><span class="token constant">K</span> <span class="token keyword">in</span> <span class="token keyword">keyof</span> Account<span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">?</span><span class="token operator">:</span> Account<span class="token punctuation">[</span><span class="token constant">K</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="custom-container tip"><p class="custom-container-title">提示</p><p>减号（-）运算符有个对应的加号（+）运算符。一般不直接使用加号运算符，因为它通常蕴含在其他运算符当中。在映射类型中，readonly 等效于 +readonly，? 等效于 +?。+ 的存在只为了确保整体协调。</p></div></div><!----><footer class="page-meta"><!----><!----><!----></footer><nav class="page-nav"><a href="/doc/readbook/%E3%80%8ATypeScript%E7%BC%96%E7%A8%8B%E3%80%8B/%E7%AC%AC%205%20%E7%AB%A0%20%E7%B1%BB%E5%92%8C%E6%8E%A5%E5%8F%A3.html" class="nav-link prev" aria-label="第 5 章 类和接口"><div class="hint"><span class="arrow left"></span>上一页</div><div class="link"><!---->第 5 章 类和接口</div></a><!----></nav><!----><!----><!--]--></main><!--]--><!----><!--]--></div><!--]--><!----><!--]--></div>
    <script type="module" src="/doc/assets/app.41440b49.js" defer></script>
  </body>
</html>
